using System;
using System.Collections.Generic;

namespace Elte.SqlSpIdx.Parser
{
    public class Parser
    {
        private Tokenizer tokenizer;
        private VariableMapper variableMapper;

        public Parser(Tokenizer tokenizer, VariableMapper variableMapper)
        {
            this.tokenizer = tokenizer;
            this.variableMapper = variableMapper;
        }

        public HalfspaceList Parse(string s, int dimension)
        {
            HalfspaceList ls = new HalfspaceList(dimension);
            List<Token> tokenlist = tokenizer.GetTokenList(s);
            Halfspace l;
            while (true)
            {
                l = GetHalfspace(tokenlist, dimension);
                ls.Add(l);

                if (!HasMoreTokens(tokenlist))
                    break;
                
                Token t = GetNext(tokenlist);
                if (t.GetType() != typeof(AND))
                    throw new ParserException(t.Position, "unexpected AND");
            }
            return ls;
        }

        private Halfspace GetHalfspace(List<Token> tokenlist, int dimension)
        {
            Halfspace l = new Halfspace(dimension);
            GetLeftSide(tokenlist, l);
            Token t = GetNext(tokenlist);
            if (t.GetType() == typeof(Elte.SqlSpIdx.Parser.LessOrGreaterThan))
            {
                LessOrGreaterThan logt = (LessOrGreaterThan)t;
                GetRightSide(tokenlist, l, dimension);
                if (logt.isLessThan == false) //gt
                {
                    l.Multiply(-1);
                }
            }
            else
            {
                throw new ParserException(t.Position, "expecting < or >");
            }
            return l;
        }

        private void GetRightSide(List<Token> tokenlist, Halfspace l, int dimension)
        {
            if (!HasMoreTokens(tokenlist))
                throw new ParserException(-1, "expecting more: a number");
            Token t = GetNext(tokenlist);
            if (t.GetType() == typeof(Number))
            {
                Number number = (Number)t;
                l.B = number.Value;
            }
            else if (t.GetType() == typeof(Sign))
            {
                Sign sign = (Sign)t;
                if (!HasMoreTokens(tokenlist))
                    throw new ParserException(-1, "expecting more: a number");
                t = GetNext(tokenlist);
                if (t.GetType() != typeof(Number))
                    throw new ParserException(t.Position, "expecting a number");
                Number number = (Number)t;
                if (sign.isPlus)
                    l.B = number.Value;
                else
                    l.B = -number.Value;
            }
            else
                throw new ParserException(t.Position, "expecting a number");
        }

        private void GetLeftSide(List<Token> tokenlist, Halfspace l)
        {
            if (!HasMoreTokens(tokenlist))
                throw new ParserException(-1, "expecting more: a left-hand side");
            bool isPlus = true;
            Token t = PeekNext(tokenlist);
            if (t.GetType() == typeof(Sign))
            {
                t = GetNext(tokenlist);
                Sign sign = (Sign)t;
                isPlus = sign.isPlus;
            }
            GetTerm(tokenlist, l, isPlus);
            while (true)
            {
                t = PeekNext(tokenlist);
                if (t.GetType() == typeof(Sign))
                {
                    t = GetNext(tokenlist);
                    Sign sign = (Sign)t;
                    GetTerm(tokenlist, l, sign.isPlus); // we either read a term or throw an exception
                }
                else
                {
                    return;
                }
            }
        }

        private void GetTerm(List<Token> tokenlist, Halfspace l, bool isPlus)
        {
            if (!HasMoreTokens(tokenlist))
                throw new ParserException(-1, "expecting more: a term");
            Token t = GetNext(tokenlist);
            if (t.GetType() == typeof(Number))
            {
                Number number = (Number)t;
                if (!HasMoreTokens(tokenlist))
                    throw new ParserException(-1, "expecting more: a * symbol");
                t = (Token)GetNext(tokenlist);
                if (t.GetType() != typeof(Times))
                    throw new ParserException(t.Position, "expecting a * symbol");
                if (!HasMoreTokens(tokenlist))
                    throw new ParserException(-1, "expecting more: a variable");
                t = (Token)GetNext(tokenlist);
                if (t.GetType() != typeof(Variable))
                    throw new ParserException(t.Position, "expecting a variable");
                Variable variable = (Variable)t;
                int varindex = this.variableMapper.GetIndex(variable.name);
                if (varindex == -1)
                    throw new ParserException(variable.Position, "no such variable: " + variable.name);
                if (isPlus)
                    l.A[varindex] += number.Value;
                else
                    l.A[varindex] -= number.Value;
            }
            else if (t.GetType() == typeof(Variable))
            {
                Variable variable = (Variable)t;
                int varindex = this.variableMapper.GetIndex(variable.name);
                if (varindex == -1)
                    throw new ParserException(variable.Position, "no such variable: " + variable.name);
                if (isPlus)
                    l.A[varindex] += 1;
                else
                    l.A[varindex] -= 1;
            }
            else
                throw new ParserException(t.Position, "expecting a term");
        }

        private bool HasMoreTokens(List<Token> tokenlist)
        {
            return (tokenlist.Count != 0);
        }

        private Token PeekNext(List<Token> tokenlist)
        {
            if (tokenlist.Count == 0)
                throw new ParserException(-1, "expecting more...");
            Token t = tokenlist[tokenlist.Count - 1];
            return t;
        }

        private Token GetNext(List<Token> tokenlist)
        {
            if (tokenlist.Count == 0)
                throw new ParserException(-1, "expecting more...");
            Token t = tokenlist[tokenlist.Count - 1];
            tokenlist.RemoveAt(tokenlist.Count - 1);
            return t;
        }
    }
}
